/* Copyright (C) 2011  Chris Mueller <ruunhb@googlemail.com>
 *
 * This file is part of the Liquid Programming Language
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "array.h"
#include "context.h"
#include "parser.h"
#include "nodes.h"
#include "symbol.h"
#include "map.h"
#include <glib/goption.h>
#include <glib/gstring.h>
#include <graphviz/gvc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

static gchar* 	arg_function = 0;
static gchar**  arg_files = 0;
static int  	arg_symbols = 0;


struct MapSymbol {
	byte* identifier;
	struct LqSymbol* symbol;
};


static GOptionEntry options[] = {
	{"function", 0, 0, G_OPTION_ARG_STRING, &arg_function, "display only the syntaxtree for a specific function", "NAME" },
	{"symbols", 's', 0, G_OPTION_ARG_NONE, &arg_symbols, "display symboltables from syntaxtree", 0},
	{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &arg_files, 0, 0},
	{ 0 }
};


Agnode_t*
render_syntaxtree(Agraph_t* g, const struct LqNode* node)
{
        if(node == 0)
                return 0;

	static int node_index = 0;
	char key[64];
	guint i;

	snprintf(key, sizeof(key), "node %d", node_index++);
	Agnode_t* n = agnode(g, key);

	if(arg_symbols && node->symbols != 0) {
		size_t count = lq_map_size(node->symbols);
		struct LqArray* buffer = lq_array_new(64, sizeof(byte));
		struct MapSymbol* pairs = lq_cast(struct MapSymbol*, lq_map_pairs(node->symbols));
		struct MapSymbol* p = pairs;

		snprintf(key, sizeof(key), "node %d", node_index++);
		Agnode_t* s = agnode(g, key);

		while(count-- > 0) {
			lq_array_append(buffer, p->identifier, strlen(p->identifier));

			if(p->symbol->kind == LQ_SYMBOL_VALUE)
				lq_array_append(buffer, " : VALUE\n", 9);
			else
				lq_array_append(buffer, " : NAMESPACE\n", 13);

			p++;
		}

		lq_array_append(buffer, "\0", 1);

		agsafeset(s, "shape", "box", "");
		agset(s, "label", lq_cast(char*, lq_array_pointer(buffer)));

		agedge(g, s, n);

		free(pairs);
	}

	guint childs = lq_node_children(lq_cast(struct LqNode*, node));

	switch(node->kind) {
		case LQ_NODE_ROOT:
			agsafeset(n, "color", "navy", "");
                        agsafeset(n, "shape", "box", "");
                case LQ_NODE_BLOCK:
			agsafeset(n, "style", "bold", "");
			break;
		case LQ_NODE_PLUS:
		case LQ_NODE_MINUS:
		case LQ_NODE_MULTIPLY:
		case LQ_NODE_DIVIDE:
		case LQ_NODE_MODULO:
		case LQ_NODE_POWER:
		case LQ_NODE_BAND:
		case LQ_NODE_BOR:
		case LQ_NODE_BNOT:
                case LQ_NODE_BXOR:
		case LQ_NODE_BITSHIFTLEFT:
		case LQ_NODE_BITSHIFTRIGHT:
		case LQ_NODE_AND:
		case LQ_NODE_OR:
		case LQ_NODE_NOT:
		case LQ_NODE_NOTEQUAL:
		case LQ_NODE_EQUAL:
		case LQ_NODE_LESS:
		case LQ_NODE_LESSEQUAL:
		case LQ_NODE_GREATER:
		case LQ_NODE_GREATEREQUAL:
		case LQ_NODE_MINUS_SIGN:
		case LQ_NODE_CONCAT:
		case LQ_NODE_REDUCE:
		case LQ_NODE_CONS:
                case LQ_NODE_INDEX:
		case LQ_NODE_ASSIGN:
			agsafeset(n, "style", "bold", "");
			break;
                case LQ_NODE_VALUE:
                        agsafeset(n, "color", "green", "");
                        agsafeset(n, "style", "bold", "");
			break;
		case LQ_NODE_LET:
			agsafeset(n, "shape", "box", "");
			agsafeset(n, "color", "green", "");
                        agsafeset(n, "style", "bold", "");
			break;
		case LQ_NODE_IF:
		case LQ_NODE_FUN:
		case LQ_NODE_MATCH:
		case LQ_NODE_BY:
			agsafeset(n, "style", "bold", "");
			break;
		case LQ_NODE_PATTERN:
			agsafeset(n, "color", "purple", "");
			agsafeset(n, "style", "bold", "");
			break;
                case LQ_NODE_MODULE:
                        agsafeset(n, "color", "red", "");
                        agsafeset(n, "style", "bold", "");
                        agsafeset(n, "shape", "box", "");

		default:
			break;

	}
	
	agset(n, "label", lq_node_to_string(node));

	for(i = 0; i < childs; ++i) {	
		struct LqNode* child = lq_node_index( lq_cast(struct LqNode*, node), i);
		Agnode_t* child_n = render_syntaxtree(g, child);

                if(child_n != 0)
		        agedge(g, n, child_n);
	}

	return n;
}




void 
display_syntaxtree(const struct LqNode* root)
{
	aginit();
	GVC_t* gvc = gvContext();
	Agraph_t* g = agopen("viz", AGDIGRAPH);

	render_syntaxtree(g, root);

	gvLayout(gvc, g, "dot");
	gvRenderFilename(gvc, g, "xlib", "ast.png");

	gvFreeLayout(gvc, g);
	agclose(g);
	gvFreeContext(gvc);
}


int 
main(int argc, char** argv)
{
	GError* error = 0;
	GString* text = g_string_sized_new(512);
	GOptionContext* opt = g_option_context_new("FILE");
	FILE* file = 0;
	char buffer[128];
	
	g_option_context_add_main_entries(opt, options, "Options");
	g_option_context_set_summary(opt, "lq-treeviz visualize an abstract syntax tree for a file/stdin by using graphviz");

	if(!g_option_context_parse(opt, &argc, &argv, &error)) {
		g_printerr("error: %s\n", error->message);
		return EXIT_FAILURE;
	}

	if(arg_files != 0)
		file = fopen(*arg_files, "r");
	if(file == 0)
		file = stdin;

	while(!feof(file) && (fgets(buffer, sizeof(buffer), file) != 0)) {
		g_string_append(text, buffer);
		memset(buffer, 0, sizeof(buffer));
	}

	lq_scanner_initialize();
	struct LqContext* context = lq_context_new(text->str, "viz");
	struct LqNode* root = lq_parse(context);

	display_syntaxtree(root);

	lq_context_free(context);
	fclose(file);

	return EXIT_SUCCESS;
}
